package com.atguigu.gulimail.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.service.coupon.CouponDubboService;
import com.atguigu.common.service.product.ProviderProductService;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberRespVo;
import com.atguigu.common.vo.SeckillOrderTo;
import com.atguigu.gulimail.seckill.interceptor.LoginUserInterceptor;
import com.atguigu.gulimail.seckill.service.SeckillService;
import com.atguigu.gulimail.seckill.to.SeckillSkuRedisTo;
import com.atguigu.gulimail.seckill.vo.SeckillSessionWithSkus;
import com.atguigu.gulimail.seckill.vo.SkuInfoVo;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author fangy
 * @date 2022-10-24 15:27
 */
@Service
public class SeckillServiceImpl implements SeckillService {

    @DubboReference(check = false)
    CouponDubboService couponDubboService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @DubboReference(check = false)
    ProviderProductService productDubboService;


    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    RedissonClient redissonClient;


    private final String SESSION_CACHE_PREFIX = "seckill:sessions";
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus";

    private final String SKU_STOCK_SEMAPHORE = "seckill:stock";

    @Override
    public void uploadSeckillSkuLatest3Days() {
        //1，扫描需要参与秒杀的活动
        R r = couponDubboService.getLates3DaySession();
        if(r.getCode() == 0){
            List<SeckillSessionWithSkus> data = r.getData(new TypeReference<List<SeckillSessionWithSkus>>() {
            });
            //缓存到redis
            //1,缓存活动信息
            saveSessionInfos(data);
            //2,缓存活动的关联商品信息
            saveSessionSkuInfos(data);
        }

    }

    @Override
    public List<SeckillSkuRedisTo> getCurrentSecKillSkus() {

        long time = new Date().getTime();

        Set<String> keys = redisTemplate.keys(SESSION_CACHE_PREFIX + "*");

        for (String key:keys) {

            String replace = key.replace(SESSION_CACHE_PREFIX, "");
            String[] s = replace.split("_");
            long startTime = Long.parseLong(s[0]);
            long endTime = Long.parseLong(s[1]);

            if(time >= startTime && time <= endTime){

                List<String> range = redisTemplate.opsForList().range(key, -100, 100);
                BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> list = ops.multiGet(range);
                if(list != null && list.size() > 0){
                    List<SeckillSkuRedisTo> collect = list.stream().map(item -> {
                        SeckillSkuRedisTo redisTo = JSON.parseObject((String) item, SeckillSkuRedisTo.class);
                        return redisTo;
                    }).collect(Collectors.toList());

                    return collect;
                }
            }
        }


        return null;

    }

    @Override
    public String kill(String killId, String key, Integer num) {

        MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

        BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);

        String json = (String) ops.get(killId);

        if(StringUtils.isEmpty(json)){
            return null;
        }else{
            SeckillSkuRedisTo redisTo = JSON.parseObject(json, SeckillSkuRedisTo.class);
            //校验合法性
            Long startTime = redisTo.getStartTime();
            Long endTime = redisTo.getEndTime();
            //1，校验时间的合法性
            long time = new Date().getTime();

            long ttl = endTime - time;

            if(time >= startTime && time <= endTime){
                //2，校验随机码和商品id
                String randomCode = redisTo.getRandomCode();
                String SkuId = redisTo.getPromotionSessionId() + "_" + redisTo.getSkuId();
                if(randomCode.equals(key) && killId.equals(SkuId)){
                    //3,验证购物数量是否合理
                    if(num <= redisTo.getSeckillLimit().intValue()){
                        //4,验证这个人是否已经购买过
                        String redisKey = memberRespVo.getId()+"_"+SkuId;
                        //利用redis的分布式锁，来判断这个人是否购买过
                        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                        if(aBoolean){
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE);

                            boolean b = semaphore.tryAcquire(num);
                            //秒杀成功
                            if(b) {
                                //快速下单，发送MQ消息
                                String timeId = IdWorker.getTimeId();
                                SeckillOrderTo seckillOrderTo = new SeckillOrderTo();

                                seckillOrderTo.setOrderSn(timeId);
                                seckillOrderTo.setMemberId(memberRespVo.getId());
                                seckillOrderTo.setNum(num);
                                seckillOrderTo.setPromotionSessionId(redisTo.getPromotionSessionId());
                                seckillOrderTo.setSeckillPrice(redisTo.getSeckillPrice());
                                seckillOrderTo.setSkuId(redisTo.getSkuId());

                                rabbitTemplate.convertAndSend("order-event-exchange", "order.seckill.order", seckillOrderTo);
                                return timeId;
                            }

                        }else{
                            //说明已经买过
                            return null;
                        }

                    }
                }else{
                    return null;
                }

            }else{
                return null;
            }

        }


        return null;

    }

    private void saveSessionInfos(List<SeckillSessionWithSkus> sessions){

        sessions.stream().forEach((item)->{
            Long startTime = item.getStartTime().getTime();
            Long endTime = item.getEndTime().getTime();

            String key = SESSION_CACHE_PREFIX+startTime+"_"+endTime;

            Boolean aBoolean = redisTemplate.hasKey(key);

            if(!aBoolean){

                List<String> collect = item.getRelationSkus().stream().map(sku -> sku.getPromotionSessionId()+"_"+sku.getSkuId().toString()).collect(Collectors.toList());

                redisTemplate.opsForList().leftPushAll(key,collect);
            }
        });

    }

    private void saveSessionSkuInfos(List<SeckillSessionWithSkus> sessions){

        sessions.stream().forEach((item)->{


            BoundHashOperations<String, Object, Object> ops = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
            item.getRelationSkus().stream().forEach(sku->{

                String token = UUID.randomUUID().toString().replace("_","");

                if(!ops.hasKey(sku.getPromotionSessionId().toString()+"_"+sku.getSkuId().toString())){
                    SeckillSkuRedisTo skuRedisTo = new SeckillSkuRedisTo();
                    //1，sku的基本数据
                    R r = productDubboService.getSkuEntityById(sku.getSkuId());
                    if(r.getCode() == 0){
                        SkuInfoVo data = r.getData(new TypeReference<SkuInfoVo>() {
                        });
                        skuRedisTo.setSkuInfo(data);
                    }

                    //2，sku的秒杀信息
                    BeanUtils.copyProperties(sku,skuRedisTo);

                    //3，设置当前商品的秒杀时间信息
                    skuRedisTo.setStartTime(item.getStartTime().getTime());
                    skuRedisTo.setEndTime(item.getEndTime().getTime());

                    //4，设置随机码
                    skuRedisTo.setRandomCode(token);

                    String skuJsonString = JSON.toJSONString(skuRedisTo);
                    ops.put(sku.getPromotionSessionId().toString()+"_"+sku.getSkuId().toString(),skuJsonString);
                }


                if(!redisTemplate.hasKey(SKU_STOCK_SEMAPHORE+token))
                {
                    //5，使用库存作为分布式的信号量  限流
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                    semaphore.trySetPermits(sku.getSeckillCount().intValue());
                    semaphore.expireAt(item.getEndTime());
                }
            });


        });

    }

}
